﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace UmlControlLibrary
{
    public class Event
    {
        public const string Added = "Added";
        public const string Created = "Created";
        public const string Modified = "Modified";
        public const string Deleted = "Deleted";


        public string Name
        {
            get;
            set;
        }

        public Type Type
        {
            get;
            set;
        }

        public string FullName
        {
            get
            {
                return (Type!=null?Type.FullName:"")+ "|" + Name;
            }
        }

        public Event(string name, Type type)
        {
            this.Name = name;
            this.Type = type;
        }
    }

    public class EventFilter : Event
    {
        public object Instance
        {
            get;
            set;
        }

        public EventFilter(string name, Type type, object instance)
            : this(name, type)
        {
            Instance = instance;
        }

        public EventFilter(string name, Type type)
            : base(name, type)
        {
        }

        public EventFilter(Event _event, object instance)
            : this(_event.Name, _event.Type, instance)
        {
        }
    }

    public class EventServiceArgs : EventArgs
    {
        public object Sender
        {
            get;
            set;
        }

        public Event Event
        {
            get;
            set;
        }

        public object Datas
        {
            get;
            set;
        }

        public EventServiceArgs(object sender, Event _event):base()
        {
            Sender = sender;
            this.Event = _event;
        }

        public EventServiceArgs(object sender, Event _event, object datas)
            : this(sender, _event)
        {
            Datas = datas;
        }
    }

    public class EventServiceValidationArgs : EventServiceArgs
    {
        public bool Canceled
        {
            get;
            set;
        }

        public EventServiceValidationArgs(EventServiceArgs args)
            :base(args.Sender, args.Event, args.Datas)
        {
            Canceled = false;
        }
    }

    public interface IEventListener
    {
        void HandleEvent(EventServiceArgs args);
    }

    public class EventService
    {
        public enum EventSourceStatus
        {
            Stopped,
            Delayed,
            Active
        }


        public class LogEvent
        {
            public static Event Event = new Event("Log", typeof(EventService));

            public object EventSender 
            {
                get;set;
            }

            public Event InnerEvent
            {
                get;set;
            }

            public DateTime TimeStamp
            {
                get;
                set;
            }

            public string Message
            {
                get;
                set;
            }

            public override string ToString()           
            {
                string s = "";
                if (Message != "")
                    s = " : " + Message;
                return TimeStamp.ToLongTimeString() + " : " + InnerEvent.Type.Name + " - " + InnerEvent.Name + " emitted by " + EventSender.ToString() + s;
            }

            public LogEvent(object sender, Event _event, string message)
            {
                EventSender = sender;
                InnerEvent = _event;
                TimeStamp = DateTime.Now;
                Message = message;
            }

            public LogEvent(object sender, Event _event) : this(sender, _event, "")
            {
            }
        }

        private static EventService instance = new EventService();

        public List<IEventListener> FilterListeners(Dictionary<EventFilter, List<IEventListener>> listenerList, EventFilter filter, bool strict)
        {
            IEnumerable<EventFilter> filteredListeners;
            if (strict)
            {
                filteredListeners = listenerList.Keys.Where(e => (e.FullName == filter.FullName) && (e.Instance == filter.Instance));
                if (filteredListeners.Count() == 0)
                    listenerList.Add(filter, new List<IEventListener>());
            }
            else
                filteredListeners = listenerList.Keys.Where(e => ((e.Name == "") || (e.Name == filter.Name))
                                                                && ((e.Type == null) || (e.Type == filter.Type))
                                                                && ((e.Instance == null) || (e.Instance == filter.Instance)));

            List<IEventListener> result = new List<IEventListener>();
            foreach (EventFilter f in filteredListeners)
                result = result.Union(listenerList[f]).ToList<IEventListener>();
            return result;
        }

        public List<IEventListener> FilterListeners(EventFilter filter, bool strict)
        {
            return FilterListeners(Listeners, filter, strict);
        }

        public List<IEventListener> FilterValidators(EventFilter filter, bool strict)
        {
            return FilterListeners(Validators, filter, strict);
        }

        private Dictionary<EventFilter, List<IEventListener>> Listeners = new Dictionary<EventFilter, List<IEventListener>>();
        private Dictionary<EventFilter, List<IEventListener>> Validators = new Dictionary<EventFilter, List<IEventListener>>();
        private Dictionary<object, EventSourceStatus> EventSources = new Dictionary<object, EventSourceStatus>();

        private List<EventServiceArgs> EventQueue = new List<EventServiceArgs>();

        private EventSourceStatus BroadcastingStatus = EventSourceStatus.Active;

        private static void RegisterListener(EventFilter filter, IEventListener listener, bool isValidator)
        {
            List<IEventListener> l = instance.FilterListeners(filter, true);
            if (!l.Contains(listener))
                instance.Listeners[filter].Add(listener);

            if (isValidator)
            {
                l = instance.FilterValidators(filter, true);
                if (!l.Contains(listener))
                    instance.Validators[filter].Add(listener);
            }

        }

        /// <summary>
        /// Register listener for a specific event name
        /// Key : event name
        /// </summary>
        /// <param name="eventName">Name of the event. Sould use string constants (for example, UmlRelation.EventNames.Created)</param>
        /// <param name="listener">Listener to register for this event</param>
        public static void RegisterListener(string eventName, IEventListener listener, bool isValidator)
        {
            EventFilter key = new EventFilter(eventName, null);
            RegisterListener(key, listener, isValidator);
        }

        /// <summary>
        /// Register listener for a specific sender object type
        /// Key : type full name
        /// </summary>
        /// <param name="type">Type of the sender object</param>
        /// <param name="listener">Listener to register for events broadcasted by the type of sender</param>
        public static void RegisterListener(Type type, IEventListener listener, bool isValidator)
        {
            EventFilter key = new EventFilter("", type);
            RegisterListener(key, listener, isValidator);
        }

        /// <summary>
        /// Register listener for any event (usefull for logs)
        /// key : empty string
        /// </summary>
        /// <param name="listener">Listener to register for any event</param>
        public static void RegisterListener(IEventListener listener, bool isValidator)
        {
            RegisterListener("", listener, isValidator);
        }

        /// <summary>
        /// Register listener for a specific sender instance
        /// key : type fullname + "-" + object hashcode
        /// </summary>
        /// <param name="instance">Instance of sender object to match</param>
        /// <param name="listener">Listener to register for this specific object</param>
        public static void RegisterListenerForInstance(object targetInstance, IEventListener listener)
        {
            EventFilter key = new EventFilter("", null, targetInstance);
            List<IEventListener> l = EventService.instance.FilterListeners(key, true);
            if (!l.Contains(listener))
                instance.Listeners[key].Add(listener);
        }

        public static void Log(object sender, Event _event, string Message)
        {
            if (_event != EventService.LogEvent.Event)
                BroadcastEvent(EventService.instance, LogEvent.Event, new LogEvent(sender, _event, Message));
        }

        private void ProcessEvent(EventServiceArgs eventArgs)
        {
            EventServiceValidationArgs validationArgs = new EventServiceValidationArgs(eventArgs);
            EventFilter filter = new EventFilter(eventArgs.Event, eventArgs.Sender);
            List<IEventListener> validators = instance.FilterValidators(filter, false);
            List<IEventListener> targets = instance.FilterListeners(filter, false);

            bool canceled = false;
            foreach (IEventListener validator in validators)
            {
                if (!canceled)
                {
                    validator.HandleEvent(validationArgs);
                    canceled |= validationArgs.Canceled;
                    if (!canceled)
                    {
                        Log(eventArgs.Sender, eventArgs.Event, "Event validated by " + validator.ToString());
                    }
                    else
                    {
                        Log(eventArgs.Sender, eventArgs.Event, "Event canceled by " + validator.ToString());
                    }
                }
            }

            if (!canceled)
            {
                // Call all event handlers
                foreach (IEventListener listener in targets)
                    listener.HandleEvent(eventArgs);

                if (eventArgs.Event != EventService.LogEvent.Event)
                    Log(eventArgs.Sender, eventArgs.Event, "");
            }
        }

        private void ProcessQueuedEvents()
        {
            if (BroadcastingStatus == EventSourceStatus.Active)
            {

                int i = 0;
                while (i < EventQueue.Count)
                {
                    EventServiceArgs e = EventQueue[i];
                    if (EventSources.ContainsKey(e.Sender))
                    {
                        if (EventSources[e.Sender] == EventSourceStatus.Stopped)
                            EventQueue.RemoveAt(i);
                        else if (EventSources[e.Sender] == EventSourceStatus.Delayed)
                            i++;
                    }
                    else
                    {
                        EventQueue.RemoveAt(i);
                        ProcessEvent(e);
                    }
                }
            }
        }

        public static void BroadcastEvent(object sender, Event _event, object datas)
        {
            if (instance.BroadcastingStatus != EventSourceStatus.Stopped)
            {
                EventServiceArgs args = new EventServiceArgs(sender, _event, datas);
                instance.EventQueue.Add(args);
                instance.ProcessQueuedEvents();
            }
        }

        public static void BroadcastEvent(object sender, Event _event)
        {
            BroadcastEvent(sender, _event, null);
        }

        /// <summary>
        /// Broadcast event to any listener registered, either by name, type, instance, or listener of any event
        /// </summary>
        /// <param name="sender">event sender</param>
        /// <param name="eventName">event name</param>
        /// <param name="datas">datas for this event (can be null)</param>
        public static void BroadcastEvent(object sender, string eventName, object datas)
        {
            Type type = sender==null?null:sender.GetType();
            BroadcastEvent(sender, new Event(eventName, type), datas);
        }

        /// <summary>
        /// Broadcast event to any listener registered, either by name, type, instance, or listener of any event
        /// </summary>
        /// <param name="sender">event sender</param>
        /// <param name="eventName">event name</param>
        public static void BroadcastEvent(object sender, string eventName)
        {
            BroadcastEvent(sender, eventName, null);
        }

        public static void StopBroadcasting(object sender)
        {
            if (!instance.EventSources.ContainsKey(sender))
                instance.EventSources.Add(sender, EventSourceStatus.Stopped);
            else
                instance.EventSources[sender] = EventSourceStatus.Stopped;
        }

        public static void DelayBroadcasting(object sender)
        {
            if (!instance.EventSources.ContainsKey(sender))
                instance.EventSources.Add(sender, EventSourceStatus.Delayed);
            else
                instance.EventSources[sender] = EventSourceStatus.Delayed;
        }

        /// <summary>
        /// Suspend all broadcasting
        /// </summary>
        public static void DelayBroadcasting()
        {
            instance.BroadcastingStatus = EventSourceStatus.Delayed;
        }

        public static void StopBroadcasting()
        {
            instance.BroadcastingStatus = EventSourceStatus.Stopped;
        }

        public static void ResumeBroadcasting()
        {
            instance.BroadcastingStatus = EventSourceStatus.Active;
            instance.ProcessQueuedEvents();
        }

        public static void ResumeBroadcasting(object sender)
        {                      
            if (instance.EventSources.ContainsKey(sender))
            {
                instance.EventSources.Remove(sender);
                instance.ProcessQueuedEvents();
            }
        }
    }
}
